package tinybase.pf;

//
//File:        PF_BufferMgr.java
//Description: PF_BufferMgr class implementation

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Random;

import tinybase.basic.BytePointer;
import tinybase.basic.IntegerWrapper;
import tinybase.basic.RedBase;
import tinybase.basic.RC;


public class PF_BufferMgr {

	public static final int INVALID_SLOT = -1;
	public static final FileDescriptor MEMORY_FD = null;
	public static final int PAGEHDR_SIZE= 4;

	// Global variable for the statistics manager
	public StatisticsMgr pStatisticsMgr;

	private PF_BufPageDesc[] bufTable;
	private HashTable hashTable;
	
	public void setHashTable(HashTable hashTable) {
		this.hashTable = hashTable;
	}

	private int numPages;
	private int pageSize;
	private int first;
	private int last;
	private int free;

	static File fLog;
	//
	// InsertFree
	//
	// Desc: Internal. Insert a slot at the head of the free list
	// In: slot - slot number to insert
	// Ret: PF return code
	//
	private RC insertFree(int slot) {
		bufTable[slot].next = free;
		free = slot;

		return RC.PF_SUCCESS;
	}

	//
	// LinkHead
	//
	// Desc: Internal. Insert a slot at the head of the used list, making
	// it the most-recently used slot.
	// In: slot - slot number to insert
	// Ret: PF return code
	//

	private RC linkHead(int slot) {
		// Set next and prev pointers of slot entry
		bufTable[slot].next = first;
		bufTable[slot].prev = INVALID_SLOT;

		// If list isn't empty, point old first back to slot
		if (first != INVALID_SLOT)
			bufTable[first].prev = slot;
		first = slot;

		// if list was empty, set last to slot
		if (last == INVALID_SLOT)
			last = first;

		return RC.PF_SUCCESS;
	}

	//
	// Unlink
	//
	// Desc: Internal. Unlink the slot from the used list. Assume that
	// slot is valid. Set prev and next pointers to INVALID_SLOT.
	// The caller is responsible to either place the unlinked page into
	// the free list or the used list.
	// In: slot - slot number to unlink
	// Ret: PF return code
	//
	private RC unlink(int slot) {
		// If slot is at head of list, set first to next element
		if (first == slot)
			first = bufTable[slot].next;

		// If slot is at end of list, set last to previous element
		if (last == slot)
			last = bufTable[slot].prev;

		// If slot not at end of list, point next back to previous
		if (bufTable[slot].next != INVALID_SLOT)
			bufTable[bufTable[slot].next].prev = bufTable[slot].prev;

		// If slot not at head of list, point prev forward to next
		if (bufTable[slot].prev != INVALID_SLOT)
			bufTable[bufTable[slot].prev].next = bufTable[slot].next;

		// Set next and prev pointers of slot entry
		bufTable[slot].prev = INVALID_SLOT;
		bufTable[slot].next = INVALID_SLOT;

		return RC.PF_SUCCESS;
	}

	//
	// InternalAlloc
	//
	// Desc: Internal. Allocate a buffer slot. The slot is inserted at the
	// head of the used list. Here's how it chooses which slot to use:
	// If there is something on the free list, then use it.
	// Otherwise, choose a victim to replace. If a victim cannot be
	// chosen (because all the pages are pinned), then return an error.
	// Out: slot - set to newly-allocated slot
	// Ret: PF_NOBUF if all pages are pinned, other PF return code otherwise
	//
	private RC internalAlloc(Slot slot) {
		RC rc; // return code
		int i = slot.getSlotValue();

		// If the free list is not empty, choose a slot from the free list
		if (free != INVALID_SLOT) {
			slot.setSlotValue(free);
			free = bufTable[slot.getSlotValue()].next;
		} else {
			// Choose the least-recently used page that is unpinned
			for (i = last; i != INVALID_SLOT; i = bufTable[i].prev) {
				if (bufTable[i].pinCount == 0)
					break;
			}

			slot.setSlotValue(i);

			// Return error if all buffers were pinned
			if (i == INVALID_SLOT)
				return RC.PF_NOBUF;

			// Write out the page if it is dirty
			if (bufTable[i].bDirty) {
				if ((rc = writePage(bufTable[i].fd, bufTable[i].pageNum,
						bufTable[i].data)) != RC.PF_SUCCESS)
					return rc;

				bufTable[i].bDirty = false;
			}

			// Remove page from the hash table and slot from the used buffer
			// list
			if ((rc = hashTable.delete(bufTable[i].fd, bufTable[i].pageNum))
					!= RC.PF_SUCCESS
					|| (rc = unlink(i)) != RC.PF_SUCCESS)
				return rc;

			slot.setSlotValue(i);
		}

		// Link slot at the head of the used list
		if ((rc = linkHead(slot.getSlotValue())) != RC.PF_SUCCESS)
			return rc;

		// Return ok
		return RC.PF_SUCCESS;
	}

	//
	// ReadPage
	//
	// Desc: Read a page from disk
	//
	// In: fd - OS file descriptor
	// pageNum - number of page to read
	// dest - pointer to buffer in which to read page
	// Out: dest - buffer contains page contents
	// Ret: PF return code
	//
	private RC readPage(FileDescriptor fd, PageNum pageNum, byte[] dest) {
		/*
		 * if define pf_log?
		 */

		String pfMessage = "Reading (" + fd + ", " + pageNum.getPageNum()
				+ "). \n";
		writeLog(pfMessage);

		/*
		 * if define pf_stats
		 */

		pStatisticsMgr.register(Statistic.PF_READPAGE,
				Stat_Operation.STAT_ADDONE, null);

		long offset = pageNum.getPageNum() * (long) pageSize
      + PF_Internal.PF_FILE_HDR_SIZE;

		FileInputStream fis = new FileInputStream(fd);
		FileChannel fc = fis.getChannel();
		ByteBuffer bb = ByteBuffer.wrap(dest);

		long num = 0;
		try {
			if ((fc = fc.position(offset)) == null)
				return RC.PF_UNIX;
			num = fc.read(bb);
		} catch (IOException e) {
			e.printStackTrace();
		}

		if (num < 0)
			return RC.PF_UNIX;
		else if (num != pageSize)
			return RC.PF_INCOMPLETEREAD;
		else
			return RC.PF_SUCCESS;

	}

	//
	// WritePage
	//
	// Desc: Write a page to disk
	//
	// In: fd - OS file descriptor
	// pageNum - number of page to write
	// dest - pointer to buffer containing page contents
	// Ret: PF return code
	//
	private RC writePage(FileDescriptor fd, PageNum pageNum,
			byte[] source) {

		/* if define PF_LOG */
		String psMessage = "Reading (" + fd + ", " + pageNum.getPageNum()
				+ "). \n";
		writeLog(psMessage);

		/* if define pf_stats */
		pStatisticsMgr.register(Statistic.PF_READPAGE,
				Stat_Operation.STAT_ADDONE, null);

    //

		long offset = pageNum.getPageNum() * (long) pageSize
      +	PF_Internal.PF_FILE_HDR_SIZE;

		FileOutputStream fos = new FileOutputStream(fd);
		FileChannel fc = fos.getChannel();
		ByteBuffer bb = ByteBuffer.wrap(source);

		long num = 0;
		try {
			if ((fc = fc.position(offset)) == null) 
				return RC.PF_UNIX;
			num= fc.write(bb);
		} catch (IOException e) {
			e.printStackTrace();
		}

		if (num < 0)
			return RC.PF_UNIX;
		else if (num != pageSize)
			return RC.PF_INCOMPLETEWRITE;
		else
			return RC.PF_SUCCESS;
	}

	//
	// InitPageDesc
	//
	// Desc: Internal. Initialize PF_BufPageDesc to a newly-pinned page
	// for a newly pinned page
	// In: fd - file descriptor
	// pageNum - page number
	// Ret: PF return code
	//
	private RC initPageDesc(FileDescriptor fd, PageNum pageNum,
			Slot slot) {

		// set the slot to refer to a newly-pinned page
		bufTable[slot.getSlotValue()].fd = fd;
		bufTable[slot.getSlotValue()].pageNum = pageNum;
		bufTable[slot.getSlotValue()].bDirty = false;
		bufTable[slot.getSlotValue()].pinCount = 1;

		// Return ok
		return RC.PF_SUCCESS;
	}

	/*
	 * Public methods
	 */

	//
	// PF_BufferMgr
	//
	// Desc: Constructor - called by Manager
	// The buffer manager manages the page buffer. When asked for a page,
	// it checks if it is in the buffer. If so, it pins the page (pages
	// can be pinned multiple times). If not, it reads it from the file
	// and pins it. If the buffer is full and a new page needs to be
	// inserted, an unpinned page is replaced according to an LRU
	// In: numPages - the number of pages in the buffer
	//
	// Note: The constructor will initialize the global pStatisticsMgr. We
	// make it global so that other components may use it and to allow
	// easy access.
	//
	// Aut2003

	public PF_BufferMgr(int numPages) {

		// Initialize local variables
		this.numPages = numPages;
		pageSize = Pf.PF_PAGE_SIZE + PAGEHDR_SIZE;
		hashTable = new HashTable(PF_Internal.PF_HASH_TBL_SIZE);

		// if define PF_STATS
		// Initialize the global variable for the statistics manager
		pStatisticsMgr = new StatisticsMgr();

		// if define PF_LOG
		String pfMessage = "Creating buffer manager. " + numPages
				+ " pages of size " + pageSize;
		writeLog(pfMessage);

		// Allocate memory for buffer page description table
		bufTable = new PF_BufPageDesc[numPages];

		// Initialize the buffer table and allocate memory for buffer pages.
		// Initially, the free list contains all pages
		for (int i = 0; i < numPages; i++) {
			bufTable[i] = new PF_BufPageDesc();
			byte[] temp = new byte[pageSize];
			if (temp == null) {
				System.err.println("Not enough memory for buffer");
				System.exit(1);
			} else {
				bufTable[i].data = temp;
			}
			for (int j = 0; j < pageSize; j++)
				bufTable[i].data[i] = '0';

			bufTable[i].prev = i - 1;
			bufTable[i].next = i + 1;
		}

		bufTable[0].prev = INVALID_SLOT;
		bufTable[numPages - 1].next = INVALID_SLOT;
		free = 0;
		first = last = INVALID_SLOT;

		// if define PF_LOG
		writeLog("Succesfully created the buffer manager.\n");
	}

	//
	// GetPage
	//
	// Desc: Get a pointer to a page pinned in the buffer. If the page is
	// already in the buffer, (re)pin the page and return a pointer
	// to it. If the page is not in the buffer, read it from the file,
	// pin it, and return a pointer to it. If the buffer is full,
	// replace an unpinned page.
	// In: fd - OS file descriptor of the file to read
	// pageNum - number of the page to read
	// bMultiplePins - if FALSE, it is an error to ask for a page that is
	// already pinned in the buffer.
	// Out: ppBuffer - set *ppBuffer to point to the page in the buffer
	// Ret: PF return code
	//

	public RC getPage(FileDescriptor fd, PageNum pageNum,
			BytePointer ppBuffer, boolean bMultiplePins) {
		RC rc;
		Slot slot = new Slot(0); // buffer slot where page is located

		// if define PF_LOG
		String psMessage = "Looking for (" + fd.toString() + ","
				+ pageNum.getPageNum() + ").\n";
		writeLog(psMessage);

		// if define PF_STATS
		pStatisticsMgr.register(Statistic.PF_GETPAGE,
				Stat_Operation.STAT_ADDONE, null);

		// Search for page in buffer
		if ((rc = hashTable.find(fd, pageNum, slot)) != RC.PF_SUCCESS
				&& (rc != RC.PF_HASHNOTFOUND))
			return rc; // unexpected error

		// if page not in buffer

		if (rc == RC.PF_HASHNOTFOUND) {

			// if define pf_stats
			pStatisticsMgr.register(Statistic.PF_PAGEFOUND,
					Stat_Operation.STAT_ADDONE, null);
			// end if

			// Allocate an empty page, this will also promote the newly
			// allocated
			// page to the MRU slot
			if ((rc = internalAlloc(slot)) != RC.PF_SUCCESS)
				return rc;

			// read the page, insert it into the hash table,
			// and initialize the page description entry
			if ((rc = readPage(fd, pageNum, bufTable[slot.getSlotValue()].data)) != RC.PF_SUCCESS
					|| (rc = hashTable.insert(fd, pageNum, slot)) != RC.PF_SUCCESS
					|| (rc = initPageDesc(fd, pageNum, slot)) != RC.PF_SUCCESS) {

				// Put the slot back on the free list before returning the error
				unlink(slot.getSlotValue());
				insertFree(slot.getSlotValue());
				return rc;
			}
			writeLog("Page not found in buffer. Loaded.\n");
		} else { // Page is in the buffer...
			// if define pf_stats
			pStatisticsMgr.register(Statistic.PF_PAGEFOUND,
					Stat_Operation.STAT_ADDONE, null);
			// end if

			// Error if we don't want to get a pinned page
			if (bMultiplePins == false
					&& bufTable[slot.getSlotValue()].pinCount > 0)
				return RC.PF_PAGEPINNED;

			// Page is already in memory, just increment pin count
			bufTable[slot.getSlotValue()].pinCount++;

			// if define pf_log
			psMessage = "Page found in buffer.  "
					+ bufTable[slot.getSlotValue()].pinCount + " pin count.\n";
			writeLog(psMessage);

			// Make this page the most recently used page
			if ((rc = unlink(slot.getSlotValue())) != RC.PF_SUCCESS
					|| (rc = linkHead(slot.getSlotValue())) != RC.PF_SUCCESS)
				return rc;
		}

		// Point ppBuffer to page
		ppBuffer.setArray(bufTable[slot.getSlotValue()].data);

		return RC.PF_SUCCESS;
	}

	//
	// AllocatePage
	//
	// Desc: Allocate a new page in the buffer and return a pointer to it.
	// In: fd - OS file descriptor of the file associated with the new page
	// pageNum - number of the new page
	// Out: ppBuffer - set *ppBuffer to point to the page in the buffer
	// Ret: PF return code
	//
	public RC allocatePage(FileDescriptor fd, PageNum pageNum,
			BytePointer ppBuffer) {
		RC rc;
		Slot slot = new Slot(0);

		String psMessage = "Allocating a page for (" + fd.toString() + ","
				+ pageNum.getPageNum() + ")....";
		System.out.println(psMessage);
		writeLog(psMessage);

		// If page is already in buffer, return an error
		if ((rc = hashTable.find(fd, pageNum, slot)) == RC.PF_SUCCESS)
			return RC.PF_PAGEINBUF;
		else if (rc != RC.PF_HASHNOTFOUND)
			return rc; // unexpected error

		// Allocate an empty page
		if ((rc = internalAlloc(slot)) != RC.PF_SUCCESS)
			return rc;

		// Insert the page into the hash table,
		// and initialize the page description entry

		if ((rc = hashTable.insert(fd, pageNum, slot)) != RC.PF_SUCCESS
				|| (rc = initPageDesc(fd, pageNum, slot)) != RC.PF_SUCCESS) {

			// Put the slot back on the free list before returning the error
			unlink(slot.getSlotValue());
			insertFree(slot.getSlotValue());
			return rc;
		}
		System.out.println("Successufully allocated page.\n");
		writeLog("Successufully allocated page.\n");

		// Point ppBuffer to page
		ppBuffer.setArray(bufTable[slot.getSlotValue()].data);
		return RC.PF_SUCCESS;
	}

	//
	// MarkDirty
	//
	// Desc: Mark a page dirty so that when it is discarded from the buffer
	// it will be written back to the file.
	// In: fd - OS file descriptor of the file associated with the page
	// pageNum - number of the page to mark dirty
	// Ret: PF return code
	//
	RC markDirty(FileDescriptor fd, PageNum pageNum) {
		RC rc;
		Slot slot = new Slot(0);

		String psMessage = "Marking dirty (" + fd.toString() + "," + pageNum
				+ ").\n";
		writeLog(psMessage);

		// The page must be found and pinned in the buffer
		if ((rc = hashTable.find(fd, pageNum, slot)) != RC.PF_SUCCESS)
			if (rc == RC.PF_HASHNOTFOUND)
				return RC.PF_PAGENOTINBUF;
			else
				return rc; // unexpected error

		if (bufTable[slot.getSlotValue()].pinCount == 0)
			return RC.PF_PAGEUNPINNED;

		// Mark this page dirty
		bufTable[slot.getSlotValue()].bDirty = true;

		// Make this page the most recently used page
		if ((rc = unlink(slot.getSlotValue())) != RC.PF_SUCCESS
				|| (rc = linkHead(slot.getSlotValue())) != RC.PF_SUCCESS)
			return rc;

		return RC.PF_SUCCESS;
	}

	//
	// UnpinPage
	//
	// Desc: Unpin a page so that it can be discarded from the buffer.
	// In: fd - OS file descriptor of the file associated with the page
	// pageNum - number of the page to unpin
	// Ret: PF return code
	//
	public RC unpinPage(FileDescriptor fd, PageNum pageNum) {
		RC rc;
		Slot slot = new Slot(0);

		// The page must be found and pinned in the buffer
		if ((rc = hashTable.find(fd, pageNum, slot)) != RC.PF_SUCCESS)
			if (rc == RC.PF_HASHNOTFOUND)
				return RC.PF_PAGENOTINBUF;
			else
				return rc; // unexpected error

		if (bufTable[slot.getSlotValue()].pinCount == 0)
			return RC.PF_PAGEUNPINNED;

		String psMessage = null;
		if (fd != null)
			psMessage = "Unpinning (" + fd.toString() + "," + pageNum	+ "). "
        + (bufTable[slot.getSlotValue()].pinCount - 1)
				+ "Pin count\n";
		else
			psMessage= "Unpinning (" + fd + "," + pageNum + "). "
        + (bufTable[slot.getSlotValue()].pinCount - 1)
        + "Pin count\n";
		writeLog(psMessage);

		// If unpinning the last pin, make it the most recently used page
		if (--(bufTable[slot.getSlotValue()].pinCount) == 0) {
			if ((rc = unlink(slot.getSlotValue())) != RC.PF_SUCCESS
					|| (rc = linkHead(slot.getSlotValue())) != RC.PF_SUCCESS)
				return rc;
		}

		return RC.PF_SUCCESS;
	}

	//
	// FlushPages
	//
	// Desc: Release all pages for this file and put them onto the free list
	// Returns a warning if any of the file's pages are pinned.
	// A linear search of the buffer is performed.
	// A better method is not needed because # of buffers are small.
	// In: fd - file descriptor
	// Ret: PF_PAGEPINNED or other PF return code
	//

	public RC flushPages(FileDescriptor fd) {
		RC rc, rcWarn = RC.PF_SUCCESS;

		String psMessage = "Flushing all pages for (" + fd.toString() + ").\n";
		writeLog(psMessage);

		pStatisticsMgr.register(Statistic.PF_FLUSHPAGES,
				Stat_Operation.STAT_ADDONE, null);

		// Do a linear scan of the buffer to find pages belonging to the file
		Slot slot = new Slot(0);
		slot.setSlotValue(first);

		while (slot.getSlotValue() != INVALID_SLOT) {
			int next = bufTable[slot.getSlotValue()].next;

			// If the page belongs to the passed-in file descriptor
			if (bufTable[slot.getSlotValue()].fd == fd) {
				// if define pf_blog
				psMessage = "Page (" + bufTable[slot.getSlotValue()].pageNum
						+ ") is in buffer manager.\n";
				writeLog(psMessage);
				// end if

				// Ensure the page is not pinned
				if (bufTable[slot.getSlotValue()].pinCount != 0) {
					rcWarn = RC.PF_PAGEPINNED;
				} else {
					// Write the page if dirty
					if (bufTable[slot.getSlotValue()].bDirty) {
						// if define pf_log
						psMessage = "Page ("
								+ bufTable[slot.getSlotValue()].pageNum
								+ ") is dirty\n";
						writeLog(psMessage);
						// end if

						if ((rc = writePage(fd,
								bufTable[slot.getSlotValue()].pageNum,
								bufTable[slot.getSlotValue()].data)) 
								!= RC.PF_SUCCESS)
							return rc;
						bufTable[slot.getSlotValue()].bDirty = false;
					}

					// Remove page from the hash table and add the slot the free
					// list
					if ((rc = hashTable.delete(fd,
							bufTable[slot.getSlotValue()].pageNum)) != RC.PF_SUCCESS
							|| (rc = unlink(slot.getSlotValue())) != RC.PF_SUCCESS
							|| (rc = insertFree(slot.getSlotValue())) != RC.PF_SUCCESS)
						return rc;
				}
			}
			slot.setSlotValue(next);
		}
		writeLog("All necessary pages flushed.\n");

		// Return warning or ok
		return rcWarn;
	}

	//
	// ForcePages
	//
	// Desc: If a page is dirty then force the page from the buffer pool
	// onto disk. The page will not be forced out of the buffer pool.
	// In: The page number, a default value of ALL_PAGES will be used if
	// the client doesn't provide a value. This will force all pages.
	// Ret: Standard PF errors
	//
	//

	public RC forcePages(FileDescriptor fd, PageNum pageNum) {
		RC rc;

		// if define pf_log
		String psMessage = "Forcing page " + fd.toString() + " for (" + pageNum
				+ ").\n";
		writeLog(psMessage);
		// end if

		// Do a linear scan of the buffer to find the page for the file
		Slot slot = new Slot(0);
		slot.setSlotValue(first);
		while (slot.getSlotValue() != INVALID_SLOT) {
			int next = bufTable[slot.getSlotValue()].next;

			// If the page belongs to the passed-in file descriptor
			if (bufTable[slot.getSlotValue()].fd == fd
					&& (pageNum.getPageNum() == RedBase.ALL_PAGES || bufTable[slot
							.getSlotValue()].pageNum == pageNum)) {
				// if define pf_log
				psMessage = "Page (" + bufTable[slot.getSlotValue()].pageNum
						+ ") is in buffer pool.\n";
				writeLog(psMessage);
				// end if

				// I don't care if the page is pinned or not, just write it if
				// it is dirty.
				if (bufTable[slot.getSlotValue()].bDirty) {
					psMessage = "Page ("
							+ bufTable[slot.getSlotValue()].pageNum
							+ ") is dirty.\n";
					writeLog(psMessage);
					if ((rc = writePage(fd,
							bufTable[slot.getSlotValue()].pageNum,
							bufTable[slot.getSlotValue()].data)) != RC.PF_SUCCESS)
						return rc;
					bufTable[slot.getSlotValue()].bDirty = false;
				}
			}
			slot.setSlotValue(next);
		}

		return RC.PF_SUCCESS;
	}

	//
	// PrintBuffer
	//
	// Desc: Display all of the pages within the buffer.
	// This routine will be called via the system command.
	// In: Nothing
	// Out: Nothing
	// Ret: Always returns 0
	//

	public RC printBuffer() {
		System.out.println("buffer contains " + numPages + " pages of size "
				+ pageSize);
		System.out.println("Contents in order from most recently used to "
				+ "least recently used");

		Slot slot = new Slot(0);
		int next;

		while (slot.getSlotValue() != INVALID_SLOT) {
			next = bufTable[slot.getSlotValue()].next;
			System.out.println(slot.getSlotValue() + " :: ");
			System.out.println("  fd= "
					+ bufTable[slot.getSlotValue()].fd.toString());
			System.out.println("  pageNum= "
					+ bufTable[slot.getSlotValue()].pageNum);
			System.out.println("  bDirty= "
					+ bufTable[slot.getSlotValue()].bDirty);
			System.out.println("  pinCount= "
					+ bufTable[slot.getSlotValue()].pinCount);
			slot.setSlotValue(next);
		}

		if (first == INVALID_SLOT)
			System.out.println("Buffer is empty!");
		else
			System.out.println("All remaining slots are free.");

		return RC.PF_SUCCESS;
	}

	//
	// ClearBuffer
	//
	// Desc: Remove all entries from the buffer manager.
	// This routine will be called via the system command and is only
	// really useful if the user wants to run some performance
	// comparison starting with an clean buffer.
	// In: Nothing
	// Out: Nothing
	// Ret: Will return an error if a page is pinned and the Clear routine
	// is called.

	public RC clearBuffer() {
		RC rc;

		Slot slot = new Slot(0);
		int next;

		while (slot.getSlotValue() != INVALID_SLOT) {
			next = bufTable[slot.getSlotValue()].next;
			if (bufTable[slot.getSlotValue()].pinCount == 0)
				if ((rc = hashTable.delete(bufTable[slot.getSlotValue()].fd,
						bufTable[slot.getSlotValue()].pageNum)) != RC.PF_SUCCESS
						|| (rc = unlink(slot.getSlotValue())) != RC.PF_SUCCESS
						|| (rc = insertFree(slot.getSlotValue())) != RC.PF_SUCCESS)
					return rc;
			slot.setSlotValue(next);
		}
		return RC.PF_SUCCESS;
	}

	//
	// ResizeBuffer
	//
	// Desc: Resizes the buffer manager to the size passed in.
	// This routine will be called via the system command.
	// In: The new buffer size
	// Out: Nothing
	// Ret: 0 for success or,
	// Some other PF error (probably PF_NOBUF)
	//
	// Notes: This method attempts to copy all the old pages which I am
	// unable to kick out of the old buffer manager into the new buffer
	// manager. This obviously cannot always be successfull!
	//

	public RC resizeBuffer(int iNewSize) {
		int i;
		RC rc;

		// First try and clear out the old buffer!
		this.clearBuffer();

		// Allocate memory for a new buffer table
		PF_BufPageDesc[] pNewBufTable = new PF_BufPageDesc[iNewSize];

		// Initialize the new buffer table and allocate memory for buffer
		// pages. Initially, the free list contains all pages
		for (i = 0; i < iNewSize; i++) {
			// String d = new byte[pageSize];
			// pNewBufTable.get(i).setData(d);
			if ((pNewBufTable[i].data = new byte[pageSize]) == null) {
				System.out.print("Not enough memory for buffer\n");
				break;
			}
			for (i = 0; i < pageSize; i++) {
				pNewBufTable[i].data = null;
			}
			pNewBufTable[i].prev = i - 1;
			pNewBufTable[i].next = i + 1;
		}
		pNewBufTable[0].prev = pNewBufTable[iNewSize - 1].next = INVALID_SLOT;

		// Now we must remember the old first and last slots and (of course)
		// the buffer table itself. Then we use insert methods to insert
		// each of the entries into the new buffertable
		int oldFirst = first;
		PF_BufPageDesc[] pOldBufTable = bufTable;

		// Setup the new number of pages, first, last and free
		numPages = iNewSize;
		first = last = INVALID_SLOT;
		free = 0;

		// Setup the new buffer table
		bufTable = pNewBufTable;

		// We must first remove from the hashtable any possible entries
		int slot, next, newSlot = 0;
		slot = oldFirst;
		while (slot != INVALID_SLOT) {
			next = pOldBufTable[slot].next;

			// Must remove the entry from the hashtable from the
			if ((rc = hashTable.delete(pOldBufTable[slot].fd,
					pOldBufTable[slot].pageNum)) != RC.PF_SUCCESS)
				return rc;
			slot = next;
		}

		// Now we traverse through the old buffer table and copy any old
		// entries into the new one
		slot = oldFirst;
		while (slot != INVALID_SLOT) {

			next = pOldBufTable[slot].next;
			// Allocate a new slot for the old page
			if ((rc = internalAlloc(new Slot(newSlot))) != RC.PF_SUCCESS)
				return (rc);

			// Insert the page into the hash table,
			// and initialize the page description entry
			if ((rc = hashTable.insert(pOldBufTable[slot].fd,
					pOldBufTable[slot].pageNum, new Slot(newSlot))) != RC.PF_SUCCESS
					|| (rc = initPageDesc(pOldBufTable[slot].fd,
							pOldBufTable[slot].pageNum, new Slot(newSlot))) != RC.PF_SUCCESS)
				return (rc);

			// Put the slot back on the free list before returning the error
			unlink(newSlot);
			insertFree(newSlot);

			slot = next;
		}
		return RC.PF_SUCCESS;
	}

	// GetBlockSize
	public RC getBlockSize(IntegerWrapper length) {
		//length = pageSize;
		length.setValue(pageSize);
		return RC.PF_SUCCESS;
	}

	// AllocateBlock
	public RC allocateBlock(BytePointer buffer) {
		RC rc = RC.PF_SUCCESS;

		// Get an empty slot from the buffer pool
		Slot slot = new Slot(0);
		if ((rc = internalAlloc(slot)) != RC.PF_SUCCESS)
			return rc;

		// Create artificial page number (just needs to be unique for hash
		// table )
//System.out.println("Generate hash value: " + bufTable[slot.getSlotValue()].data.hashCode());		
		PageNum pageNum = new PageNum(bufTable[slot.getSlotValue()].data.hashCode());

		// Insert the page into the hash table, and initialize the page
		// description entry
		if ((rc = hashTable.insert(MEMORY_FD, pageNum, slot)) != RC.PF_SUCCESS
				|| (rc = initPageDesc(MEMORY_FD, pageNum, slot)) != RC.PF_SUCCESS) {
			// Put the slot back on the free list before returning the error
			unlink(slot.getSlotValue());
			insertFree(slot.getSlotValue());
			return rc;
		}

		// Return pointer to buffer
		buffer.setArray(bufTable[slot.getSlotValue()].data);

		// Return success code
		return RC.PF_SUCCESS;
	}

	// DisposeBlock
	public RC disposeBlock(byte[] buffer) {
//System.out.println("Dispose hashcode: " + buffer.hashCode());
		return unpinPage(MEMORY_FD, new PageNum(buffer.hashCode()));
	}

	//
	// writeLog
	//
	// This is a self contained unit that will create a new log file and send
	// psMessage to the log file. Notice that I do not close the file fLog at
	// any time. Hopefully if all goes well this will be done when the program
	// exits.
	//

	public void writeLog(String psMessage) {

		FileWriter fw = null;
		String psFileName = null;

		// The first time through we have to create a new Log file
		if (fLog == null) {
			// This is the first time so I need to create a new log file.
			// The log file will be named "PF_LOG.x" where x is the next
			// available sequential number
			int iLogNum = -1;
			boolean bFound = false;

			while (iLogNum < 999 && bFound == false) {
				iLogNum++;
				psFileName = "log/PF_LOG." + iLogNum;
				try {
					fLog = new File(psFileName);
					if (fLog.createNewFile()) {
						bFound = true;
					}
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

			if (!bFound) {
				System.err.println("Cannot create a new log file!");
				System.exit(1);
			}
		}

		// Now we have the log file open and ready for writing
		try {
			fw = new FileWriter(fLog, true);
			fw.write(psMessage + "\n");
			fw.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
